#if !defined(SHADOWRENDERER_H)
#define SHADOWRENDERER_H

#include "ShadowUtils.h"

#define OMNI_SIDES_NUM 6

struct ShadowMapFrustum
{
	Matrix44A mLightProjMatrix;
	Matrix44A mLightViewMatrix;
	
	// flags
	bool	bAllowViewDependency;
	bool	bUseVarianceSM;
	bool	bUseAdditiveBlending;
	// flag to indicate large-area terrain shadows, so it's
	// flagged independent of if additive blending is used
	bool	bTerrainShadows; 

	bool	bForSubSurfScattering;
	
	// if set to true - pCastersList contains all casters in light radius 
	// and all other members related only to single frustum projection case are undefined
	bool	bOmniDirectionalShadow;
	uint8	nOmniFrustumMask;
	uint8	nInvalidatedFrustMask[MAX_GPU_NUM]; //for each GPU
	uint8	nShadowGenMask;

	bool bHWPCFCompare;
	bool bUseHWShadowMap;
	bool bUseFilter;
	bool bReflectiveShadowMap;		// for RSMs

	bool bNormalizedDepth;

	uint8 nShadowPoolUpdateRate;

	float fDepthShift; //precision range shift for FP shadow maps


	//sampling parameters 
	f32 fWidthS,fWidthT;
	f32 fBlurS,fBlurT;
	int32 numSamples;

  //temporal solution for frustum bounds
  //they are NDC z coords
  f32 fMinFrustumBound;
  f32 fMaxFrustumBound;

  //test
  Vec3 vMaxBoundPoint;

  //fading distance per light source
  float fShadowFadingDist;


  ETEX_Format             m_eReqTF;
  ETEX_Type               m_eReqTT;
  //bool bFreeTexture;

  //texture in pool
  //struct SDynTexture_Shadow** ppDepthTex; //temp solution for multi threading
  bool bUseShadowsPool;
  struct ShadowMapFrustum* pFrustumOwner;
  struct SDynTexture_Shadow* pDepthTex;
  struct SDynTextureArray* pDepthTexArray;

  //3d engine parameters
  float fFOV;
  float fNearDist;
  float fFarDist;
  int   nTexSize;

  //shadow renderer parameters - should be in separate structure
  //atlas parameters
  int   nTextureWidth;
  int   nTextureHeight;
  bool  bUnwrapedOmniDirectional;
  int   nShadowMapSize;
  //int   fShadowMapWidth, fShadowMapHeight;

  //packer params
  uint nPackID[OMNI_SIDES_NUM];
  int packX[OMNI_SIDES_NUM];
  int packY[OMNI_SIDES_NUM];
  int packWidth[OMNI_SIDES_NUM];
  int packHeight[OMNI_SIDES_NUM];

  int   nResetID;
	float fFrustrumSize;
	float fProjRatio;
	float fDepthTestBias;
  float fDepthConstBias;
  float fDepthSlopeBias;
  PodArray<struct IShadowCaster*> * pCastersList;
	CCamera FrustumPlanes[OMNI_SIDES_NUM];
	AABB aabbCasters; //casters bbox in world space
	float fCameraFarPlaneGSM; //far plane value for current GSM lod
	Vec3 vLightSrcRelPos; // relative world space
	Vec3 vProjTranslation; // dst position
	float fRadius;
	int nDLightId;
	int nUpdateFrameId;
//	int nAffectsReceiversFrameId;
	IRenderNode * pLightOwner;
	uint32 uCastersListCheckSum;
	int nShadowMapLod;			// currently use as GSMLod, can be used as cubemap side, -1 means this variable is not used

//	float fMaxDistanceOfTheRecivers[6];														//max distances / face (if it's a cubemap) for determine which object we needed
//	float f_OldMaxDistanceOfTheRecivers[6];														//max distances / face (if it's a cubemap) for determine which object we needed
	uint32	m_Flags;

  ShadowMapFrustum()
  {
    ZeroStruct(*this);
		fProjRatio = 1.f;
		nDLightId = -1;

    fMinFrustumBound = 0.0f;
    fMaxFrustumBound = 1.0f;

    //initial frustum position should be outside of the visible map
    vProjTranslation = Vec3(-1000.0f, -1000.0f, -1000.0f);

    bUnwrapedOmniDirectional = false;
    nShadowMapSize = 0;

		bReflectiveShadowMap = false;

		nUpdateFrameId = -1000;
  }

  ~ShadowMapFrustum()
  {
    delete pCastersList;
    //SAFE_DELETE((IDynTexture*)pDepthTexArray);
    //SAFE_RELEASE((IDynTexture*)pDepthTex);
  }

  void GetSideViewport( int nSide, int* pViewport)
  {
    if (bUseShadowsPool)
    {
      pViewport[0] = packX[nSide];
      pViewport[1] = packY[nSide];
      pViewport[2] = packWidth[nSide];
      pViewport[3] = packHeight[nSide];
    }
    else
    {
      //simplest cubemap 6 faces unwrap
      pViewport[0] = nShadowMapSize * (nSide%3);
      pViewport[1] = nShadowMapSize * (nSide/3);
      pViewport[2] = nShadowMapSize;
      pViewport[3] = nShadowMapSize;
    }
  }

  void GetTexOffset( int nSide, float* pOffset, float* pScale, int nShadowsPoolSizeX, int nShadowsPoolSizeY)
  {
    if (bUseShadowsPool)
    {
      pScale[0] = float(nShadowMapSize)/nShadowsPoolSizeX; //SHADOWS_POOL_SZ 1024
      pScale[1] = float(nShadowMapSize)/nShadowsPoolSizeY;
      pOffset[0] = float(packX[nSide])/nShadowsPoolSizeX;
      pOffset[1] = float(packY[nSide])/nShadowsPoolSizeY;
    }
    else
    {
      pOffset[0] = 1.0f/3.0f * (nSide%3);
      pOffset[1] = 1.0f/2.0f * (nSide/3);
      pScale[0] = 1.0f/3.0f;
      pScale[1] = 1.0f/2.0f;
    }
  }

	void RequestUpdate()
	{
    for (int i=0; i<MAX_GPU_NUM; i++)
  		nInvalidatedFrustMask[i] = 0x3F;
	}

	bool isUpdateRequested(int nMaskNum)
	{
    assert(nMaskNum>=0 && nMaskNum<MAX_GPU_NUM);
    /*if (nMaskNum==-1) //request from 3dengine
    {
      for (int i=0; i<MAX_GPU_NUM; i++)
      {
        if(nInvalidatedFrustMask[i]>0)
          return true;
      }
      return false;
    }*/

		return (nInvalidatedFrustMask[nMaskNum]>0);
	}

  ILINE bool IntersectAABB(const AABB & bbox, bool * pAllIn) const
	{
		if(bOmniDirectionalShadow)
		{
			return bbox.IsOverlapSphereBounds(vLightSrcRelPos+vProjTranslation, fFarDist);
		}
		else
		{
			return FrustumPlanes[0].IsAABBVisible_EH(bbox, pAllIn) > 0;
		}
	}

  ILINE bool IntersectSphere(const Sphere & sp, bool * pAllIn)
  {
    if(bOmniDirectionalShadow)
      return Distance::Point_PointSq(sp.center, vLightSrcRelPos+vProjTranslation) < sqr(fFarDist+sp.radius);
    else
    {
      uint8 res = FrustumPlanes[0].IsSphereVisible_FH(sp);
      *pAllIn = (res == CULL_INCLUSION);
      return res != CULL_EXCLUSION;
    }
  }

  void UnProject(float sx, float sy, float sz, float *px, float *py, float *pz, IRenderer * pRend)
  {
    const int shadowViewport[4] = {0,0,1,1};
			
		Matrix44A mIden;
		mIden.SetIdentity();

		//FIX remove float arrays
    pRend->UnProject(sx,sy,sz,
      px,py,pz,
      (float*)&mLightViewMatrix,
      (float*)&mIden,
      shadowViewport);
  }

  Vec3 & UnProjectVertex3d(int sx, int sy, int sz, Vec3 & vert, IRenderer * pRend)
  {
    float px; 
    float py; 
    float pz;
    UnProject((float)sx, (float)sy, (float)sz, &px, &py, &pz, pRend);
    vert.x=(float)px;
    vert.y=(float)py;
    vert.z=(float)pz;

//		pRend->DrawBall(vert,10);

    return vert;
  }
  
	void UpdateOmniFrustums()
	{
		const float sCubeVector[6][7] = 
		{
			{1,0,0,  0,0,1, -90}, //posx
			{-1,0,0, 0,0,1,  90}, //negx
			{0,1,0,  0,0,-1, 0},  //posy
			{0,-1,0, 0,0,1,  0},  //negy
			{0,0,1,  0,1,0,  0},  //posz
			{0,0,-1, 0,1,0,  0},  //negz 
		};

		const Vec3 vPos = vLightSrcRelPos + vProjTranslation;
		for (int nS = 0; nS < OMNI_SIDES_NUM; ++nS)
		{
			const Vec3 vForward   = Vec3(sCubeVector[nS][0], sCubeVector[nS][1], sCubeVector[nS][2]);
			const Vec3 vUp        = Vec3(sCubeVector[nS][3], sCubeVector[nS][4], sCubeVector[nS][5]);
			const Matrix33 matRot = Matrix33::CreateOrientation(vForward, vUp, DEG2RAD(sCubeVector[nS][6]));
			const float fFOV = bUnwrapedOmniDirectional ? (float)DEG2RAD_R(g_fOmniShadowFov) : (float)DEG2RAD_R(90.0f);

			FrustumPlanes[nS].SetMatrix(Matrix34(matRot, vPos));
			FrustumPlanes[nS].SetFrustum(nTexSize, nTexSize, fFOV, fNearDist, fFarDist);
		}
	}

  void DrawFrustum(IRenderer * pRend, int nFrames = 1)
  {
		if(abs(nUpdateFrameId - pRend->GetFrameID())>nFrames)
			return;

		//if(!arrLightViewMatrix[0] && !arrLightViewMatrix[5] && !arrLightViewMatrix[10])
			//return;

		IRenderAuxGeom * pRendAux = pRend->GetIRenderAuxGeom();
  
    Vec3 vert1,vert2;
		ColorB c0(255,255,255,255);
    {
      pRendAux->DrawLine(
        UnProjectVertex3d(0,0,0,vert1,pRend), c0,
        UnProjectVertex3d(0,0,1,vert2,pRend), c0);

      pRendAux->DrawLine(
        UnProjectVertex3d(1,0,0,vert1,pRend), c0,
        UnProjectVertex3d(1,0,1,vert2,pRend), c0);

      pRendAux->DrawLine(
        UnProjectVertex3d(1,1,0,vert1,pRend), c0,
        UnProjectVertex3d(1,1,1,vert2,pRend), c0);

      pRendAux->DrawLine(
        UnProjectVertex3d(0,1,0,vert1,pRend), c0,
        UnProjectVertex3d(0,1,1,vert2,pRend), c0);
    }

    for(int i=0; i<=1; i++)
    {
      pRendAux->DrawLine(
        UnProjectVertex3d(0,0,i,vert1,pRend), c0,
        UnProjectVertex3d(1,0,i,vert2,pRend), c0);

      pRendAux->DrawLine(
        UnProjectVertex3d(1,0,i,vert1,pRend), c0,
        UnProjectVertex3d(1,1,i,vert2,pRend), c0);

      pRendAux->DrawLine(
        UnProjectVertex3d(1,1,i,vert1,pRend), c0,
        UnProjectVertex3d(0,1,i,vert2,pRend), c0);

      pRendAux->DrawLine(
        UnProjectVertex3d(0,1,i,vert1,pRend), c0,
        UnProjectVertex3d(0,0,i,vert2,pRend), c0);
    }
  }

	void GetMemoryUsage(ICrySizer * pSizer) const; 
	
};

#endif
